package org.gap.jseed;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.gap.jseed.utils.Defense;


/*package*/ class ArgumentsToConstructorMatcher {
	private Constructor<?> constructor;
	private StrategyMatcher strategy;

	public ArgumentsToConstructorMatcher(Class<?> cl, Object...args) throws NoSuchMethodException {
		for (Constructor<?> each : cl.getDeclaredConstructors()) {
			strategy = createObjectToClassStrategy(each.getParameterTypes());
			if (hasMatchingArgumentsWithParameters(each.getParameterTypes(), args)) {
				constructor = each;
				break;
			}
		}
		if (constructor == null) {
			throw new NoSuchMethodException();
		}
	}
	
	public StrategyMatcher createObjectToClassStrategy(Class<?>[] args) {
		StrategyMatcher result = new DefaultStrategyMatcher();
		for (Class<?> each : args) {
			if (each.isPrimitive()) {
				result = createStrategyIfPrimitiveType(each, int.class, Integer.class, result);
				result = createStrategyIfPrimitiveType(each, char.class, Character.class, result);
				result = createStrategyIfPrimitiveType(each, double.class, Double.class, result);
				result = createStrategyIfPrimitiveType(each, float.class, Float.class, result);
				result = createStrategyIfPrimitiveType(each, long.class, Long.class, result);
				result = createStrategyIfPrimitiveType(each, int.class, Integer.class, result);
				result = createStrategyIfPrimitiveType(each, short.class, Short.class, result);
				result = createStrategyIfPrimitiveType(each, byte.class, Byte.class, result);
				result = createStrategyIfPrimitiveType(each, boolean.class, Boolean.class, result);
			}
		}
		return result;
	}

	private StrategyMatcher createStrategyIfPrimitiveType(Class<?> arg, Class<?> primitive, Class<?> object, StrategyMatcher result) {
		if (arg.equals(primitive)) 
			result = new ClassStrategyMatcher(object, primitive, result);
		return result;
	}

	private boolean hasMatchingArgumentsWithParameters(Class<?>[] parameterTypes, Object[] args) {
		ArrayList<Class<?>> list = new ArrayList<Class<?>>();
		for (Object each : args) {
			Defense.argumentNotNull(each);
			list.add(strategy.isMatching(each));
		}
		
		return haveSameClasses(parameterTypes, list.toArray(new Class<?>[list.size()]));
	}
	
	protected static boolean haveSameClasses(Class<?>[] first, Class<?>[] second) {
		List<Class<?>> firstArgs = Arrays.asList(first);
		List<Class<?>> secondArgs = Arrays.asList(second);
		
		if (firstArgs.size() == secondArgs.size()) {
			for (Class<?> each : secondArgs) {
				if (!firstArgs.contains(each)) {
					return false;
				}
			}
			return true;
		}
		return false;
	}


	public Constructor<?> getConstructor() {
		return constructor;
	}
	
	/*package*/ interface StrategyMatcher {
		public Class<?> isMatching(Object argument);
	}
	
	/*package*/ static class ClassStrategyMatcher implements StrategyMatcher {
		private Class<?> objectClass;
		private Class<?> primitiveClass;
		private StrategyMatcher nextLink;

		public ClassStrategyMatcher(Class<?> objectClass, Class<?> primitiveClass) {
			this(objectClass, primitiveClass, new DefaultStrategyMatcher());
		}
		
		public ClassStrategyMatcher(Class<?> objectClass, Class<?> primitiveClass, StrategyMatcher nextLink) {
			this.objectClass = objectClass;
			this.primitiveClass = primitiveClass;
			this.nextLink = nextLink;
		}
		
		@Override
		public Class<?> isMatching(Object argument) {
			if (objectClass.isInstance(argument)) {
				return primitiveClass;
			} 
			return nextLink.isMatching(argument);
		}
	}
	
	/*package*/ static class DefaultStrategyMatcher implements StrategyMatcher{
		
		@Override
		public Class<?> isMatching(Object argument) {
			return argument.getClass();
		}
	}
}
